﻿using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;

namespace Garfield.Presentation.Controls
{
    public class CustomWindow : Window
    {
        static CustomWindow()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof (CustomWindow), new FrameworkPropertyMetadata(typeof (CustomWindow)));
        }

        public static readonly RoutedCommand CloseCommand = new RoutedCommand();
        public static readonly RoutedCommand MinimizeCommand = new RoutedCommand();
        public static readonly RoutedCommand RestoreCommand = new RoutedCommand();
        public static readonly RoutedCommand MaximizeCommand = new RoutedCommand();

        public CustomWindow()
        {
            CommandBindings.Add(new CommandBinding(MinimizeCommand, (sender, e) =>
                                                                        {
                                                                            OnWindowMinimized();
                                                                            e.Handled = true;
                                                                        }));
            CommandBindings.Add(new CommandBinding(MaximizeCommand, (sender, e) =>
                                                                        {
                                                                            OnWindowMaximized((bool) e.Parameter);
                                                                            e.Handled = true;
                                                                        }));
            CommandBindings.Add(new CommandBinding(CloseCommand, (sender, e) =>
                                                                     {
                                                                         Close();
                                                                         e.Handled = true;
                                                                     }));
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            var title = (UIElement) Template.FindName("PART_Title", this);
            title.AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(delegate { DragMove(); }));

            if (ResizeMode == ResizeMode.NoResize) return;
            ApplyResizableBorder();
        }

        private void ApplyResizableBorder()
        {
            var topBorder = (UIElement) Template.FindName("PART_TopBorder", this);
            var bottomBorder = (UIElement) Template.FindName("PART_BottomBorder", this);
            var leftBorder = (UIElement) Template.FindName("PART_LeftBorder", this);
            var rightBorder = (UIElement) Template.FindName("PART_RightBorder", this);

            var topLeftCorner = (UIElement) Template.FindName("PART_TopLeft", this);
            var topRightCorner = (UIElement) Template.FindName("PART_TopRight", this);
            var bottomLeftCorner = (UIElement) Template.FindName("PART_BottomLeft", this);
            var bottomRightCorner = (UIElement) Template.FindName("PART_BottomRight", this);

            topBorder.AddHandler(MouseLeftButtonDownEvent,
                                 new MouseButtonEventHandler(delegate { DragSize(windowHandle, SizingAction.North); }));
            bottomBorder.AddHandler(MouseLeftButtonDownEvent,
                                    new MouseButtonEventHandler(delegate { DragSize(windowHandle, SizingAction.South); }));
            leftBorder.AddHandler(MouseLeftButtonDownEvent,
                                  new MouseButtonEventHandler(delegate { DragSize(windowHandle, SizingAction.West); }));
            rightBorder.AddHandler(MouseLeftButtonDownEvent,
                                   new MouseButtonEventHandler(delegate { DragSize(windowHandle, SizingAction.East); }));

            topLeftCorner.AddHandler(MouseLeftButtonDownEvent,
                                     new MouseButtonEventHandler(
                                         delegate { DragSize(windowHandle, SizingAction.NorthWest); }));
            topRightCorner.AddHandler(MouseLeftButtonDownEvent,
                                      new MouseButtonEventHandler(
                                          delegate { DragSize(windowHandle, SizingAction.NorthEast); }));
            bottomLeftCorner.AddHandler(MouseLeftButtonDownEvent,
                                        new MouseButtonEventHandler(
                                            delegate { DragSize(windowHandle, SizingAction.SouthWest); }));
            bottomRightCorner.AddHandler(MouseLeftButtonDownEvent,
                                         new MouseButtonEventHandler(
                                             delegate { DragSize(windowHandle, SizingAction.SouthEast); }));
            windowHandle = new WindowInteropHelper(this).Handle;
            HwndSource.FromHwnd(windowHandle).AddHook(WindowProc);
        }

        protected virtual void OnWindowMinimized()
        {
            WindowState = WindowState.Minimized;
        }

        protected virtual void OnWindowMaximized(bool maximized)
        {
            WindowState = maximized ? WindowState.Maximized : WindowState.Normal;
        }

        #region Win32 Hack

        private IntPtr windowHandle;

        private const int WM_SYSCOMMAND = 0x112;
        private const int SC_SIZE = 0xF000;

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        private static void DragSize(IntPtr handle, SizingAction sizingAction)
        {
            if (Mouse.LeftButton == MouseButtonState.Pressed)
            {
                SendMessage(handle, WM_SYSCOMMAND, (IntPtr) (SC_SIZE + sizingAction), IntPtr.Zero);
                SendMessage(handle, 514, IntPtr.Zero, IntPtr.Zero);
            }
        }

        public enum SizingAction
        {
            North = 3,
            South = 6,
            East = 2,
            West = 1,
            NorthEast = 5,
            NorthWest = 4,
            SouthEast = 8,
            SouthWest = 7
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;

            public POINT(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MINMAXINFO
        {
            public POINT ptReserved;
            public POINT ptMaxSize;
            public POINT ptMaxPosition;
            public POINT ptMinTrackSize;
            public POINT ptMaxTrackSize;
        } ;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class MONITORINFO
        {
            public int cbSize = Marshal.SizeOf(typeof (MONITORINFO));

            public RECT rcMonitor = new RECT();

            public RECT rcWork = new RECT();

            public int dwFlags = 0;
        }


        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;

            public static readonly RECT Empty = new RECT();

            public int Width
            {
                get { return Math.Abs(right - left); }
            }

            public int Height
            {
                get { return bottom - top; }
            }

            public RECT(int left, int top, int right, int bottom)
            {
                this.left = left;
                this.top = top;
                this.right = right;
                this.bottom = bottom;
            }


            public RECT(RECT rcSrc)
            {
                left = rcSrc.left;
                top = rcSrc.top;
                right = rcSrc.right;
                bottom = rcSrc.bottom;
            }

            public bool IsEmpty
            {
                get { return left >= right || top >= bottom; }
            }

            public override string ToString()
            {
                if (this == Empty)
                {
                    return "RECT {Empty}";
                }
                return
                    "RECT { left : " + left + " / top : " + top + " / right : " + right + " / bottom : " + bottom + " }";
            }

            public override bool Equals(object obj)
            {
                if (!(obj is Rect))
                {
                    return false;
                }
                return (this == (RECT) obj);
            }

            public override int GetHashCode()
            {
                return left.GetHashCode() + top.GetHashCode() + right.GetHashCode() + bottom.GetHashCode();
            }


            public static bool operator ==(RECT rect1, RECT rect2)
            {
                return
                    (rect1.left == rect2.left && rect1.top == rect2.top && rect1.right == rect2.right &&
                     rect1.bottom == rect2.bottom);
            }

            public static bool operator !=(RECT rect1, RECT rect2)
            {
                return !(rect1 == rect2);
            }
        }

        [DllImport("user32")]
        internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);

        [DllImport("User32")]
        internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);

        private static void HandleGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
        {
            var mmi = (MINMAXINFO) Marshal.PtrToStructure(lParam, typeof (MINMAXINFO));

            // Adjust the maximized size and position to fit the work area of the correct monitor
            var MONITOR_DEFAULTTONEAREST = 0x00000002;
            var monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

            if (monitor != IntPtr.Zero)
            {
                var monitorInfo = new MONITORINFO();
                GetMonitorInfo(monitor, monitorInfo);
                var rcWorkArea = monitorInfo.rcWork;
                var rcMonitorArea = monitorInfo.rcMonitor;
                mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
                mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top + 3);
                // don't care about the width...should use maximized one.
                //mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
                //mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
            }

            Marshal.StructureToPtr(mmi, lParam, true);
        }

        private static IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            switch (msg)
            {
                case 0x0024:
                    HandleGetMinMaxInfo(hwnd, lParam);
                    handled = false;
                    break;
            }

            return (IntPtr) 0;
        }

        #endregion
    }
}